{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# 手动CPA攻击"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "SCOPETYPE = 'OPENADC'\n",
    "PLATFORM = 'CWLITEARM'\n",
    "CRYPTO_TARGET = 'TINYAES128C'\n",
    "num_traces = 50\n",
    "CHECK_CORR = False"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "%%bash -s \"$PLATFORM\" \"$CRYPTO_TARGET\"\n",
    "cd ../hardware/victims/firmware/simpleserial-aes\n",
    "make PLATFORM=$1 CRYPTO_TARGET=$2"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## CPA攻击理论"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "请查看[相关能力分析](https://wiki.newae.com/Correlation_Power_Analysis)了解CPA攻击的背景知识，同时建议你应该先完成B1部分的教程，因为这将介绍jupyter并展示如何使用python与ChipWhisperer交互。假设你已经完成了该部分那么我们便继续。\n",
    "\n",
    "当你读到此处，我们需要先完成一些事情：\n",
    "\n",
    "1. 在执行AES加密时获取目标的一些能量轨迹\n",
    "2. 读取包括模拟波形（轨迹）和发送到加密核心的数据\n",
    "3. 制作能量泄漏模型，它需要已知明文以及一位密钥字节的猜测\n",
    "4. 实现相关方程，循环遍历所有轨迹\n",
    "5. 对相关方程的输出排序以确定最可能的值\n",
    "\n",
    "本篇将使用ChipWhisperer记录轨迹并使用CPA进行攻击。"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 捕获能量轨迹"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "捕获能量轨迹和之前的教程一样，不同的是这次我们我们将会循环捕获多条能轨，并使用numpy存储他们。这不是必要的，但是我们仍然使用`散景图`来绘制这些能轨"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "\n",
    "### 设置"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "我们会使用一些帮助脚本使得编码更加容易，如果你使用XMEGA或STM（CWLITEARM）板子，那么下面应该设置应该运行正确："
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "%run \"Helper_Scripts/Setup_Generic.ipynb\""
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "fw_path = '../hardware/victims/firmware/simpleserial-aes/simpleserial-aes-{}.hex'.format(PLATFORM)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "cw.program_target(scope, prog, fw_path)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 捕获轨迹"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "在接下来的循环中，每次将会加载一些新明文，指定示波器并且发送密钥和明文。最终记录将会返回一个新的轨迹到`traces[]`列表，最后，我们将其转换为numpy的数组，为了我们接下来的分析操作。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "#Capture Traces\n",
    "from tqdm import tnrange\n",
    "import numpy as np\n",
    "import time\n",
    "\n",
    "ktp = cw.ktp.Basic()\n",
    "\n",
    "traces = []\n",
    "\n",
    "for i in tnrange(num_traces, desc='Capturing traces'):\n",
    "    key, text = ktp.next()  # manual creation of a key, text pair can be substituted here\n",
    "    trace = cw.capture_trace(scope, target, text, key)\n",
    "    if trace is None:\n",
    "        continue\n",
    "    traces.append(trace)\n",
    "\n",
    "#Convert traces to numpy arrays\n",
    "trace_array = np.asarray([trace.wave for trace in traces])  # if you prefer to work with numpy array for number crunching\n",
    "textin_array = np.asarray([trace.textin for trace in traces])\n",
    "known_keys = np.asarray([trace.key for trace in traces])  # for fixed key, these keys are all the same"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "现在我们拥有了轨迹，我们可以使用它们来绘制`散景图`:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "from bokeh.plotting import figure, show\n",
    "from bokeh.io import output_notebook\n",
    "\n",
    "output_notebook()\n",
    "p = figure()\n",
    "\n",
    "xrange = range(len(traces[0].wave))\n",
    "p.line(xrange, traces[2].wave, line_color=\"red\")\n",
    "show(p)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# cleanup the connection to the target and scope\n",
    "scope.dis()\n",
    "target.dis()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 轨迹分析"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 使用轨迹数据"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "现在我们得到了一些轨迹，让我们看看这些记录，轨迹数据存储在`trace_array`中，`textin_array`存储用来加密的明文数据，现在，让我门获取一些关于轨迹的基本信息（轨迹总数，每条轨迹中的采样点数），之后我们会用到它们："
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "numtraces = np.shape(trace_array)[0] #total number of traces\n",
    "numpoints = np.shape(trace_array)[1] #samples per trace"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "为了分析，我们需要遍历密钥中我们想攻击的每一个字节，以及每个轨迹，例如：\n",
    "```python\n",
    "for bnum in range(0, 16):\n",
    "    for tnum in range(0, numtraces):\n",
    "        pass\n",
    "```\n",
    "接下来让我们分析AES算法实现`pass`部分的代码"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 计算相关性"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "现在我们能够利用目标的能轨，让我们回顾AES的工作流程，记得我们的目标是获取图中底部的位置（即攻击S盒）\n",
    "\n",
    "![title](https://wiki.newae.com/images/7/71/Sbox_cpa_detail.png)\n",
    "\n",
    "所以我们的目标是得到S盒的输出，其中S盒的代码实现如下："
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "sbox = (\n",
    "    0x63, 0x7c, 0x77, 0x7b, 0xf2, 0x6b, 0x6f, 0xc5, 0x30, 0x01, 0x67, 0x2b, 0xfe, 0xd7, 0xab, 0x76,\n",
    "    0xca, 0x82, 0xc9, 0x7d, 0xfa, 0x59, 0x47, 0xf0, 0xad, 0xd4, 0xa2, 0xaf, 0x9c, 0xa4, 0x72, 0xc0,\n",
    "    0xb7, 0xfd, 0x93, 0x26, 0x36, 0x3f, 0xf7, 0xcc, 0x34, 0xa5, 0xe5, 0xf1, 0x71, 0xd8, 0x31, 0x15,\n",
    "    0x04, 0xc7, 0x23, 0xc3, 0x18, 0x96, 0x05, 0x9a, 0x07, 0x12, 0x80, 0xe2, 0xeb, 0x27, 0xb2, 0x75,\n",
    "    0x09, 0x83, 0x2c, 0x1a, 0x1b, 0x6e, 0x5a, 0xa0, 0x52, 0x3b, 0xd6, 0xb3, 0x29, 0xe3, 0x2f, 0x84,\n",
    "    0x53, 0xd1, 0x00, 0xed, 0x20, 0xfc, 0xb1, 0x5b, 0x6a, 0xcb, 0xbe, 0x39, 0x4a, 0x4c, 0x58, 0xcf,\n",
    "    0xd0, 0xef, 0xaa, 0xfb, 0x43, 0x4d, 0x33, 0x85, 0x45, 0xf9, 0x02, 0x7f, 0x50, 0x3c, 0x9f, 0xa8,\n",
    "    0x51, 0xa3, 0x40, 0x8f, 0x92, 0x9d, 0x38, 0xf5, 0xbc, 0xb6, 0xda, 0x21, 0x10, 0xff, 0xf3, 0xd2,\n",
    "    0xcd, 0x0c, 0x13, 0xec, 0x5f, 0x97, 0x44, 0x17, 0xc4, 0xa7, 0x7e, 0x3d, 0x64, 0x5d, 0x19, 0x73,\n",
    "    0x60, 0x81, 0x4f, 0xdc, 0x22, 0x2a, 0x90, 0x88, 0x46, 0xee, 0xb8, 0x14, 0xde, 0x5e, 0x0b, 0xdb,\n",
    "    0xe0, 0x32, 0x3a, 0x0a, 0x49, 0x06, 0x24, 0x5c, 0xc2, 0xd3, 0xac, 0x62, 0x91, 0x95, 0xe4, 0x79,\n",
    "    0xe7, 0xc8, 0x37, 0x6d, 0x8d, 0xd5, 0x4e, 0xa9, 0x6c, 0x56, 0xf4, 0xea, 0x65, 0x7a, 0xae, 0x08,\n",
    "    0xba, 0x78, 0x25, 0x2e, 0x1c, 0xa6, 0xb4, 0xc6, 0xe8, 0xdd, 0x74, 0x1f, 0x4b, 0xbd, 0x8b, 0x8a,\n",
    "    0x70, 0x3e, 0xb5, 0x66, 0x48, 0x03, 0xf6, 0x0e, 0x61, 0x35, 0x57, 0xb9, 0x86, 0xc1, 0x1d, 0x9e,\n",
    "    0xe1, 0xf8, 0x98, 0x11, 0x69, 0xd9, 0x8e, 0x94, 0x9b, 0x1e, 0x87, 0xe9, 0xce, 0x55, 0x28, 0xdf,\n",
    "    0x8c, 0xa1, 0x89, 0x0d, 0xbf, 0xe6, 0x42, 0x68, 0x41, 0x99, 0x2d, 0x0f, 0xb0, 0x54, 0xbb, 0x16)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "因此我们编写了一个函数，传入明文中单个字节和我们猜测的密钥，返回经过S盒的输出值。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "def intermediate(pt, keyguess):\n",
    "    return sbox[pt ^ keyguess]"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "最后，记得我们需要获得猜测值的汉明重量（HW），我们假设这个系统会将S盒的HW泄漏出来，一个常规的方式是将数字转为二进制之后数1的数量。\n",
    "\n",
    "```python\n",
    ">>> bin(0x1F)\n",
    "'0b11111'\n",
    ">>> bin(0x1F).count('1')\n",
    "5\n",
    "```\n",
    "但是它太慢了，更好的方法是构造一个查询表"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "HW = [bin(n).count(\"1\") for n in range(0, 256)]"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 检查操作"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "我们的目标是计算以下值：\n",
    "\n",
    "$$r_{i,j} = \\frac{\\sum_{d=1}^{D}[(h_{d,i} - \\bar{h_i})(t_{d,j}-\\bar{t_j})]}{\\sqrt{\\sum_{d=1}^D(h_{d,i}-\\bar{h_i})^2\\sum_{d=1}^D(t_{d,j}-\\bar{t_j})^2}}$$\n",
    "\n",
    "有\n",
    "\n",
    "| **等式** | **Python变量** | **值**  | \n",
    "|--------------|---------------------|------------|\n",
    "|  d           |       tnum          | 能轨数 |\n",
    "|  i           |       kguess        | 猜测的子密钥 |\n",
    "| j | j index trace point | 能轨中的样本数 |\n",
    "| h | hypint | 估计的功耗 | \n",
    "| t | traces | 能轨 | \n",
    "\n",
    "其中共有三个和式，对于这个初始实现，虽然使用numpy处理大型数组会更快，但我们将更明确的知道这些和式的计算方法。我们将三个和式记为变量，方程转化为如下格式：\n",
    "\n",
    "$$r_{i,j}=\\frac{sumnum}{\\sqrt{snumden1 * sumden2}}$$\n",
    "\n",
    "其中:\n",
    "\n",
    "$$sumnum = \\sum_{d=1}^{D}[(h_{d,i} - \\bar{h_i})(t_{d,j}-\\bar{t_j})]$$\n",
    "\n",
    "$$sumden1 = \\sum_{d=1}^D(h_{d,i}-\\bar{h_i})^2$$\n",
    "\n",
    "$$sumden2 = \\sum_{d=1}^D(t_{d,j}-\\bar{t_j})^2$$\n",
    "\n",
    "我们可以看到我们需要$\\bar{h_i}$和$\\bar{t_j}$，所以我们开始构建代码，在CPA的教程中，我们可以看到$h_{d,i}$仅仅是对于能轨$d$中子密钥$i$的估计功耗。我们会用到之前定义的`HW`数组以及`intermediate`函数。\n",
    "\n",
    "```python\n",
    "for bnum in range(0, 16):\n",
    "    cpaoutput = [0]*256\n",
    "    maxcpa = [0]*256\n",
    "    for kguess in range(0, 256):\n",
    "        hyp = np.zeros(numtraces)\n",
    "        for tnum in range(0, numtraces):\n",
    "            hyp[tnum] = HW[intermediate(pt[tnum][bnum], kguess)]\n",
    "```\n",
    "\n",
    "现在我们可以得到$\\bar{h_i}$:\n",
    "```python\n",
    "meanh = np.mean(hyp, dtype=np.float64)\n",
    "```\n",
    "\n",
    "以及$\\bar{t_j}$仅仅是我们能轨中的平均值:\n",
    "\n",
    "```python\n",
    "meant = np.mean(traces, axis=0, dtype=np.float64)\n",
    "```\n",
    "接下来，我们对刚刚计算的$h_{d,i}$和$t_{d,j}$进行求和\n",
    "```python\n",
    "#For each trace, do the following\n",
    "for tnum in range(numtraces):\n",
    "    hdiff = (hyp[tnum] - meanh)\n",
    "    tdiff = traces[tnum,:] - meant\n",
    "\n",
    "    sumnum = sumnum + (hdiff*tdiff)\n",
    "    sumden1 = sumden1 + hdiff*hdiff \n",
    "    sumden2 = sumden2 + tdiff*tdiff\n",
    "```\n",
    "\n",
    "现在我们可以得到每个猜测的子密钥的相关性，记为`cpaoutput[]`\n",
    "```python\n",
    "cpaoutput[kguess] = sumnum / np.sqrt( sumden1 * sumden2 )\n",
    "```\n",
    "\n",
    "我们来使用这些相关性来确定最可能的子密钥，首先我们只需要关系相关性的绝对值（这里是线性相关），无需考虑符号，此外虽然在计算相关性的时候我们没有进行考虑，但是记住每条轨迹上都是由一群样本点组成的，这意味着我们实际拥有的是每个猜测的子密钥和每个样本点的相关性。通常来说，轨迹中只有几个点是有相关的，并且它将是整个轨迹中的最大值，所以我们可以通过以下方式为每个猜测的子密钥选择相关性：\n",
    "\n",
    "```python\n",
    "maxcpa[kguess] = max(abs(cpaoutput[kguess]))\n",
    "```\n",
    "\n",
    "Finally, we can find the subkey that best matches our data by finding the one with the biggest correlation:\n",
    "\n",
    "最后，我们将通过最大相关性找到最可能的子密钥。\n",
    "\n",
    "```python\n",
    "bestguess[bnum] = np.argmax(maxcpa)\n",
    "```"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "#### 最终脚本"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "import numpy as np\n",
    "from tqdm import tqdm\n",
    "\n",
    "sbox = (\n",
    "    0x63, 0x7c, 0x77, 0x7b, 0xf2, 0x6b, 0x6f, 0xc5, 0x30, 0x01, 0x67, 0x2b, 0xfe, 0xd7, 0xab, 0x76,\n",
    "    0xca, 0x82, 0xc9, 0x7d, 0xfa, 0x59, 0x47, 0xf0, 0xad, 0xd4, 0xa2, 0xaf, 0x9c, 0xa4, 0x72, 0xc0,\n",
    "    0xb7, 0xfd, 0x93, 0x26, 0x36, 0x3f, 0xf7, 0xcc, 0x34, 0xa5, 0xe5, 0xf1, 0x71, 0xd8, 0x31, 0x15,\n",
    "    0x04, 0xc7, 0x23, 0xc3, 0x18, 0x96, 0x05, 0x9a, 0x07, 0x12, 0x80, 0xe2, 0xeb, 0x27, 0xb2, 0x75,\n",
    "    0x09, 0x83, 0x2c, 0x1a, 0x1b, 0x6e, 0x5a, 0xa0, 0x52, 0x3b, 0xd6, 0xb3, 0x29, 0xe3, 0x2f, 0x84,\n",
    "    0x53, 0xd1, 0x00, 0xed, 0x20, 0xfc, 0xb1, 0x5b, 0x6a, 0xcb, 0xbe, 0x39, 0x4a, 0x4c, 0x58, 0xcf,\n",
    "    0xd0, 0xef, 0xaa, 0xfb, 0x43, 0x4d, 0x33, 0x85, 0x45, 0xf9, 0x02, 0x7f, 0x50, 0x3c, 0x9f, 0xa8,\n",
    "    0x51, 0xa3, 0x40, 0x8f, 0x92, 0x9d, 0x38, 0xf5, 0xbc, 0xb6, 0xda, 0x21, 0x10, 0xff, 0xf3, 0xd2,\n",
    "    0xcd, 0x0c, 0x13, 0xec, 0x5f, 0x97, 0x44, 0x17, 0xc4, 0xa7, 0x7e, 0x3d, 0x64, 0x5d, 0x19, 0x73,\n",
    "    0x60, 0x81, 0x4f, 0xdc, 0x22, 0x2a, 0x90, 0x88, 0x46, 0xee, 0xb8, 0x14, 0xde, 0x5e, 0x0b, 0xdb,\n",
    "    0xe0, 0x32, 0x3a, 0x0a, 0x49, 0x06, 0x24, 0x5c, 0xc2, 0xd3, 0xac, 0x62, 0x91, 0x95, 0xe4, 0x79,\n",
    "    0xe7, 0xc8, 0x37, 0x6d, 0x8d, 0xd5, 0x4e, 0xa9, 0x6c, 0x56, 0xf4, 0xea, 0x65, 0x7a, 0xae, 0x08,\n",
    "    0xba, 0x78, 0x25, 0x2e, 0x1c, 0xa6, 0xb4, 0xc6, 0xe8, 0xdd, 0x74, 0x1f, 0x4b, 0xbd, 0x8b, 0x8a,\n",
    "    0x70, 0x3e, 0xb5, 0x66, 0x48, 0x03, 0xf6, 0x0e, 0x61, 0x35, 0x57, 0xb9, 0x86, 0xc1, 0x1d, 0x9e,\n",
    "    0xe1, 0xf8, 0x98, 0x11, 0x69, 0xd9, 0x8e, 0x94, 0x9b, 0x1e, 0x87, 0xe9, 0xce, 0x55, 0x28, 0xdf,\n",
    "    0x8c, 0xa1, 0x89, 0x0d, 0xbf, 0xe6, 0x42, 0x68, 0x41, 0x99, 0x2d, 0x0f, 0xb0, 0x54, 0xbb, 0x16)\n",
    "\n",
    "def intermediate(pt, keyguess):\n",
    "    return sbox[pt ^ keyguess]\n",
    "\n",
    "HW = [bin(n).count(\"1\") for n in range(0, 256)]\n",
    "\n",
    "numtraces = np.shape(trace_array)[0] #total number of traces\n",
    "numpoint = np.shape(trace_array)[1] #samples per trace\n",
    "\n",
    "pt = textin_array\n",
    "knownkey = traces[0].key\n",
    "cparefs = [0] * 16\n",
    "bestguess = [0]*16\n",
    "\n",
    "for bnum in tqdm(range(0, 16), desc='Attacking subkeys'):\n",
    "    cpaoutput = [0] * 256\n",
    "    maxcpa = [0] * 256\n",
    "    for kguess in range(0, 256):\n",
    "\n",
    "        # Initialize arrays &amp; variables to zero\n",
    "        sumnum = np.zeros(numpoint)\n",
    "        sumden1 = np.zeros(numpoint)\n",
    "        sumden2 = np.zeros(numpoint)\n",
    "\n",
    "        hyp = np.zeros(numtraces)\n",
    "        for tnum in range(0, numtraces):\n",
    "            hyp[tnum] = HW[intermediate(pt[tnum][bnum], kguess)]\n",
    "\n",
    "        # Mean of hypothesis\n",
    "        meanh = np.mean(hyp, dtype=np.float64)\n",
    "\n",
    "        # Mean of all points in trace\n",
    "        meant = np.mean(trace_array, axis=0, dtype=np.float64)\n",
    "\n",
    "        # For each trace, do the following\n",
    "        for tnum in range(0, numtraces):\n",
    "            hdiff = (hyp[tnum] - meanh)\n",
    "            tdiff = trace_array[tnum, :] - meant\n",
    "\n",
    "            sumnum = sumnum + (hdiff * tdiff)\n",
    "            sumden1 = sumden1 + hdiff * hdiff\n",
    "            sumden2 = sumden2 + tdiff * tdiff\n",
    "\n",
    "        cpaoutput[kguess] = sumnum / np.sqrt(sumden1 * sumden2)\n",
    "        maxcpa[kguess] = max(abs(cpaoutput[kguess]))\n",
    "\n",
    "    bestguess[bnum] = np.argmax(maxcpa)\n",
    "    cparefs[bnum] = np.argsort(maxcpa)[::-1]\n",
    "\n",
    "print(\"Best Key Guess: \", end=\"\")\n",
    "for b in bestguess: print(\"%02x \" % b, end=\"\")"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 检查我们的预测"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "由于密钥是我们生成的，所以我们可以来检验和我们的预测是否正确："
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "for b in knownkey: print(\"%02x \"%b, end=\"\")\n",
    "print(\"\\n\")\n",
    "if all([known_byte == guess_byte for known_byte, guess_byte in zip(knownkey, bestguess)]):\n",
    "    print(\"Guess was right\")\n",
    "else:\n",
    "    print(\"Guess was wrong\")"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "\n",
    "我们是用了部分猜测熵（PGE）。\n",
    "\n",
    "PGE用于衡量正确答案排行，这需要我们知道操作过程中的实际密钥，当然我们是知道的，所以我们可以来进行计算PGE。\n",
    "\n",
    "某些攻击会在期间使用不同的密钥，这意味着需要所有已知密钥数组，因为没有固定的密钥。在我们的例子中，我们已经得到了`knownkey`。\n",
    "\n",
    "以前，我们只是打印下每个子密钥的最最大输出：\n",
    "\n",
    "```python\n",
    "#Find maximum value of key\n",
    "bestguess[bnum] = np.argmax(maxcpa)\n",
    "```\n",
    "\n",
    "现在我们为了对CPA输出进行排序，我们将使用numpy中的`argsort()`函数，它将输出一个列表，其中第一个元素是最小值的索引，第二个是次小值的索引......因为在我们的输入列表中，`mapcpa`向量的索引对应猜测的密钥，这将使得我们知道密钥在哪里，我们反转该列表以便将最大CPA输出作为第一个元素：\n",
    "\n",
    "```python\n",
    "cparefs = np.argsort(maxcpa)[::-1]\n",
    "```\n",
    "\n",
    "Finally, the Partial Guessing Entropy is simply the location of the known correct key byte inside that array. We can find that with the `.index()` function:\n",
    "\n",
    "```python\n",
    "print(np.where(cparefs[0] == 0x2B))\n",
    "```\n",
    "\n",
    "正确的密钥来自我们的`knownkey`变量而不是二进制数据，把它们放在一起："
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "pge = [0]*16\n",
    "for bnum in range(0, 16):\n",
    "    pge[bnum] = np.where(cparefs[bnum] == knownkey[bnum])[0][0]\n",
    "    \n",
    "print(\"PGE: \", end=\"\")\n",
    "for i in pge:\n",
    "    print(i, end=\"\")\n",
    "print(\"\")"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 改进"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "目前实现的相关性函数将循环遍历所有轨迹，理想情况我们想进行实时计算，我们可以一边添加轨迹，同时查看其输出。在生成PGE与轨迹数量的图时这非常方便，否则我们需要进行多次循环。\n",
    "\n",
    "我们也可以使用另一种方式计算相关性函数，它将存储这些变量的和，通过此方式能让我门的计算更加简单，因为添加新轨迹时候只需要更新这个和即可，方程将变成：\n",
    "\n",
    "$$r_{i,j} = \\frac{D\\sum_{d=1}^{D}h_{d,i}t_{d,j}-\\sum_{d=1}^{D}h_{d,i}\\sum_{d=1}^{D}t_{d,j}}{\\sqrt{((\\sum_{d=1}^Dh_{d,i})^2-D\\sum_{d=1}^Dh_{d,i}^2)-((\\sum_{d=1}^Dt_{d,j})^2-D\\sum_{d=1}^Dh_{d,j}^2)}}$$"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Tests"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "assert all([known_byte == guess_byte for known_byte, guess_byte in zip(knownkey, bestguess)]), \"Failed to break encryption key\\nGot: {}\\nExpected: {}\\n\".format(knownkey, bestguess)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "assert not (np.all(pge)), \"PGE not zero for byte: {}\".format(pge)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": []
  }
 ],
 "metadata": {
  "language_info": {
   "name": "python",
   "pygments_lexer": "ipython3"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 2
}
